﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using System.Collections;
namespace TradeSea
{
    class Terrain
    {
        VertexPositionNormalTexture[] vertices; // Vertex array
        VertexBuffer vertexBuffer;
        short[] indices; // Index array
        IndexBuffer indexBuffer;
        float[,] heights; // Array of vertex heights
        float height; // Maximum height of terrain is pass in from LoadContent in main game
        float cellSize; // Distance between vertices on x and z axes
        int width, length; // Number of vertices on x and z axes
        int nVertices, nIndices; // Number of vertices and indices used to draw
        Effect effect; // Effect used for rendering
        GraphicsDevice GraphicsDevice;
        Texture2D heightMap; // Heightmap texture, this is used to get height of island at each position (1pixel = vertices height)
        Texture2D RTexture, BTexture, GTexture, WeightMap;
        public Texture2D DetailTexture;
        float DetailDistance = 2500;
        float DetailTextureTiling = 100;
        int mapWidth = 80 * 150;
        int mapLength = 80 * 150;

        Random r = new Random();
        //BillboardSystem trees;

        Vector3 location = new Vector3(0, -20, 0); //hhacked in location

        Texture2D baseTexture;
        float textureTiling;
        Vector3 lightDirection;

        public Terrain(Texture2D HeightMap, float CellSize, float Height,
        Texture2D BaseTexture, float TextureTiling, Vector3 LightDirection,
        GraphicsDevice GraphicsDevice, ContentManager Content)
        {
            this.baseTexture = BaseTexture;
            this.textureTiling = TextureTiling;
            this.lightDirection = LightDirection;
            this.heightMap = HeightMap;
            this.width = HeightMap.Width;
            this.length = HeightMap.Height;

            this.cellSize = CellSize;
            this.height = Height;

            this.GraphicsDevice = GraphicsDevice;

            //made own effect file, later on may use other effect file for multitexturing
            effect = Content.Load<Effect>("TerrainEffect");

            // 1 vertex per pixel
            nVertices = width * length;

            // (Width-1) * (Length-1) cells, 2 triangles per cell, 3 indices per
            // triangle
            nIndices = (width - 1) * (length - 1) * 6;

            vertexBuffer = new VertexBuffer(GraphicsDevice,
            typeof(VertexPositionNormalTexture), nVertices,
            BufferUsage.WriteOnly);

            indexBuffer = new IndexBuffer(GraphicsDevice,
            IndexElementSize.SixteenBits,
            nIndices, BufferUsage.WriteOnly);

            WeightMap = Content.Load<Texture2D>("weightMap");
            DetailTexture = Content.Load<Texture2D>("noise_texture");
            RTexture = Content.Load<Texture2D>("sand");
            GTexture = Content.Load<Texture2D>("rock");
            BTexture = Content.Load<Texture2D>("grass");

            getHeights();
            createVertices();
            createIndices();
            genNormals();

            vertexBuffer.SetData<VertexPositionNormalTexture>(vertices);
            indexBuffer.SetData<short>(indices);
        }

        public float[,] getPossitionArray
        {
            get{return heights;}
        }

        private void getHeights()
        {
            // Extract pixel data
            Color[] heightMapData = new Color[width * length];
            heightMap.GetData<Color>(heightMapData);

            // Create heights[,] array
            heights = new float[width, length];

            // For each pixel
            for (int y = 0; y < length; y++)
                for (int x = 0; x < width; x++)
                {
                    // Get color value (0 - 255)
                    float amt = heightMapData[y * width + x].R;

                    // Scale to (0 - 1)
                    amt /= 255.0f;

                    // Multiply by max height to get final height
                    heights[x, y] = amt * height;
                }
        }

        private void createVertices()
        {
         vertices = new VertexPositionNormalTexture[nVertices];
 
         // Calculate the position offset that will center the terrain at (0, 0, 0)
         Vector3 offsetToCenter = -new Vector3(((float)width / 2.0f) *
         cellSize, 0, ((float)length / 2.0f) * cellSize);

         // For each pixel in the image
         for (int z = 0; z < length; z++)
           for (int x = 0; x < width; x++)
           {
            // Find position based on grid coordinates and height in heightmap
           Vector3 position = new Vector3(x * cellSize,
           heights[x, z], z * cellSize) + offsetToCenter;

           // UV coordinates range from (0, 0) at grid location (0, 0) to (1, 1) at grid location (width, length)
          Vector2 uv = new Vector2((float)x / width, (float)z / length);

          // Create the vertex
          vertices[z * width + x] = new VertexPositionNormalTexture(
              position, Vector3.Zero, uv);
          }
        }

        private void createIndices()
        {
            indices = new short[nIndices];

            int i = 0;

            // For each cell
            for (short x = 0; x < width - 1; x++)
                for (short z = 0; z < length - 1; z++)
                {
                    // Find the indices of the corners
                    short upperLeft = Convert.ToInt16(z * width + x);
                    short upperRight = Convert.ToInt16(upperLeft + 1);
                    short lowerLeft = Convert.ToInt16(upperLeft + width);
                    short lowerRight = Convert.ToInt16(lowerLeft + 1);

                    // Specify upper triangle
                    indices[i++] = upperLeft;
                    indices[i++] = upperRight;
                    indices[i++] = lowerLeft;

                    // Specify lower triangle
                    indices[i++] = lowerLeft;
                    indices[i++] = upperRight;
                    indices[i++] = lowerRight;
                }
        }

        private void genNormals()
        {
            // For each triangle
            for (int i = 0; i < nIndices; i += 3)
            {
                // Find the position of each corner of the triangle
                Vector3 v1 = vertices[indices[i]].Position;
                Vector3 v2 = vertices[indices[i + 1]].Position;
                Vector3 v3 = vertices[indices[i + 2]].Position;

                // Cross the vectors between the corners to get the normal
                Vector3 normal = Vector3.Cross(v1 - v2, v1 - v3);
                normal.Normalize();

                // Add the influence of the normal to each vertex in the triangle
                vertices[indices[i]].Normal += normal;
                vertices[indices[i + 1]].Normal += normal;
                vertices[indices[i + 2]].Normal += normal;
            }

            // Average the influences of the triangles touching each vertex
            for (int i = 0; i < nVertices; i++)
                vertices[i].Normal.Normalize();
        }

        // Returns the height and steepness of the terrain at point (X, Z)
        public float GetHeightAtPosition(float X, float Z, out float Steepness)
        {
            // Clamp coordinates to locations on terrain
            X = MathHelper.Clamp(X, (-width / 2) * cellSize,
            (width / 2) * cellSize);
            Z = MathHelper.Clamp(Z, (-length / 2) * cellSize,
            (length / 2) * cellSize);

            // Map from (-Width/2->Width/2,-Length/2->Length/2)
            // to (0->Width, 0->Length)
            X += (width / 2f) * cellSize;
            Z += (length / 2f) * cellSize;

            // Map to cell coordinates
            X /= cellSize;
            Z /= cellSize;

            // Truncate coordinates to get coordinates of top left cell vertex
            int x1 = (int)X;
            int z1 = (int)Z;

            // Try to get coordinates of bottom right cell vertex
            int x2 = x1 + 1 == width ? x1 : x1 + 1;
            int z2 = z1 + 1 == length ? z1 : z1 + 1;

            // Get the heights at the two corners of the cell
            float h1 = heights[x1, z1];
            float h2 = heights[x2, z2];

            // Determine steepness (angle between higher and lower vertex of
            // cell)
            Steepness = (float)Math.Atan(Math.Abs((h1 - h2)) / (cellSize *
            Math.Sqrt(2)));

            // Find the average of the amounts lost from coordinates during
            // truncation above
            float leftOver = ((X - x1) + (Z - z1)) / 2f;

            // Interpolate between the corner vertices' heights
            return MathHelper.Lerp(h1, h2, leftOver);
        }

        public bool boatCollision(Vector3 boatPos)
        {
            int x = Convert.ToInt32(boatPos.X);
            int z = Convert.ToInt32(boatPos.Z);
            // Retrieve height
            float steepness;
            float height = GetHeightAtPosition(x, z, out steepness) + 50;

            if ( height > 70)
            {
                return true;
            }
            else if (boatPos.X >= mapWidth / 2 || boatPos.Z >= mapLength / 2 || boatPos.X <= -1 * mapWidth/2 || boatPos.Z <= -1 * mapLength/2)
            {
                return true;
            }
            return false;
        }
        public void Draw(Matrix view, Matrix projection)
        {
            //set up indices and vertexs to draw
            this.GraphicsDevice.Indices = indexBuffer;
            this.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            //set effect file varibles
            effect.CurrentTechnique = effect.Techniques["Technique1"];
            effect.Parameters["View"].SetValue(Matrix.CreateTranslation(this.location) * view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["BaseTexture"].SetValue(baseTexture);
            effect.Parameters["TextureTiling"].SetValue(textureTiling);
            effect.Parameters["LightDirection"].SetValue(lightDirection);
            effect.Parameters["RTexture"].SetValue(RTexture);
            effect.Parameters["GTexture"].SetValue(GTexture);
            effect.Parameters["BTexture"].SetValue(BTexture);
            effect.Parameters["WeightMap"].SetValue(WeightMap);
            effect.Parameters["DetailTexture"].SetValue(DetailTexture);
            effect.Parameters["DetailDistance"].SetValue(DetailDistance);
            effect.Parameters["DetailTextureTiling"].SetValue(DetailTextureTiling);


            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                //draw
                pass.Apply();
                this.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices/ 3);
            }

        }
    }
}
